/**
 *  julive
 *
 *  Create by songli on 2021/07/07
 *  Copyright © 2021 居理买房网. All rights reserved.
 */

package client

import (
	"context"
	"fmt"
	"io"
	"time"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
	grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
	"github.com/opentracing/opentracing-go"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	"git.julive.com/julive/shorturl/server/proto/shorturl"
	"git.julive.com/micro/pkg/tracing"
)

type ShorturlClient struct {
	addr   string
	tracer opentracing.Tracer
	closer io.Closer
}

func NewShorturlClient(name string) (*ShorturlClient, error) {
	service := viper.GetString("rpc." + name + ".service")
	port := viper.GetInt("rpc." + name + ".port")
	addr := fmt.Sprintf("%s:%d", service, port)

	traceName := viper.GetString("tracing.name.client")
	tracer, closer, _ := tracing.InitTracer(traceName)

	return &ShorturlClient{
		addr:   addr,
		tracer: tracer,
		closer: closer,
	}, nil
}

func (tc *ShorturlClient) CreateShorturl(ctx context.Context, req *shorturl.CreateShorturlRequest) (*shorturl.CreateShorturlResponse, error) {
	logger := ctxzap.Extract(ctx).With(zap.String("layer", "shorturlClient"), zap.String("func", "CreateShorturl"))

	rsp, err := tc.invoke(ctx, req, "CreateShorturl")
	if err != nil {
		logger.Error("ShorturlClient invoke err", zap.Error(err))
	}

	return rsp.(*shorturl.CreateShorturlResponse), errors.WithMessage(err, "shorturlClient.CreateShorturl")
}

func (tc *ShorturlClient) GetLongurl(ctx context.Context, req *shorturl.GetLongurlRequest) (*shorturl.GetLongurlResponse, error) {
	logger := ctxzap.Extract(ctx).With(zap.String("layer", "shorturlClient"), zap.String("func", "GetShorturl"))

	rsp, err := tc.invoke(ctx, req, "GetShorturl")
	if err != nil {
		logger.Error("ShorturlClient invoke err", zap.Error(err))
	}

	return rsp.(*shorturl.GetLongurlResponse), errors.WithMessage(err, "shorturlClient.GetShorturlById")
}

func (tc *ShorturlClient) invoke(ctx context.Context, req interface{}, funcName string) (interface{}, error) {
	retryOpts := []grpc_retry.CallOption{
		grpc_retry.WithMax(3),
		grpc_retry.WithPerRetryTimeout(3 * time.Second),
		grpc_retry.WithCodes(codes.NotFound, codes.Aborted),
	}

	zapOpts := []grpc_zap.Option{
		grpc_zap.WithDurationField(grpc_zap.DurationToDurationField),
	}

	conn, err := grpc.Dial(tc.addr, grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
			grpc_retry.UnaryClientInterceptor(retryOpts...),
			grpc_zap.UnaryClientInterceptor(ctxzap.Extract(ctx), zapOpts...),
			grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(tc.tracer)),
		)),
		grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(
			grpc_zap.StreamClientInterceptor(ctxzap.Extract(ctx), zapOpts...),
			grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(tc.tracer)),
		)))
	if err != nil {
		return nil, errors.Wrap(err, "shorturlClient.invoke")
	}
	defer conn.Close()

	shorturlClient := shorturl.NewShorturlServiceClient(conn)

	var rsp interface{}
	switch r := req.(type) {
	case *shorturl.CreateShorturlRequest:
		rsp, err = shorturlClient.CreateShorturl(ctx, r)
	case *shorturl.GetLongurlRequest:
		rsp, err = shorturlClient.GetLongurl(ctx, r)
	default:
		err = errors.New("shorturlClient: Unsupported request type")
	}
	return rsp, err
}

func (tc *ShorturlClient) Close() error {
	return tc.closer.Close()
}
